using System;
using System.Collections.Generic;
using FireStarter.Base;
using FireStarter.Base.BusinessObjectMerger;
using Firestarter.DB.Schema.BO;
using Habanero.Base;
using Habanero.BO;
using Habanero.BO.ClassDefinition;
using Habanero.Smooth;
using Habanero.Testability;
using NUnit.Framework;

namespace Firestarter.Test.Base.BusinessObjectMerger
{
    [TestFixture]
    public class TestMergeBOCompareBO
    {

        [TestFixtureSetUp]
        public virtual void TestFixtureSetup()
        {
            ClassDef.ClassDefs.Clear();
            AllClassesAutoMapper.ClassDefCol = ClassDef.ClassDefs;
            ClassDefCol classDefCol = typeof(FakeMergeableParent).Assembly.MapClasses();
            ClassDef.ClassDefs.Add(classDefCol);
        }

        [SetUp]
        public virtual void SetupTest()
        {
            BORegistry.DataAccessor = new DataAccessorInMemory();
        }

        [Test]
// ReSharper disable InconsistentNaming
        public void Test_CompareBusinessObjects_WhenYBOIsNull_ShouldReturnFalse()

        {
            //---------------Set up test pack-------------------
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(new MockOnePropertyBO(), null);
            //---------------Test Result -----------------------
            Assert.IsFalse(result.AreEqual);
        }


        [Test]
        public void Test_CompareBusinessObjects_WhenXBOIsNull_ShouldReturnFalse()
        {
            //---------------Set up test pack-------------------
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(null, new MockOnePropertyBO());
            //---------------Test Result -----------------------
            Assert.IsFalse(result.AreEqual);
        }

        [Test]
        public void Test_CompareBusinessObjects_WhenBothBOsAreNull_ShouldReturnTrue()
        {
            //---------------Set up test pack-------------------
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(null, null);
            //---------------Test Result -----------------------
            Assert.IsTrue(result.AreEqual);
        }


        [Test]
        public void Test_CompareBusinessObjects_WhenBOsAreDifferentTypes_ShouldThrowInvalidCastException()
        {
            //---------------Set up test pack-------------------
            var mergeBO = new MergeBO<MockOnePropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            try
            {
                mergeBO.CompareBusinessObjects(new MockOnePropertyBO(), new MockTwoPropertyBO());
                Assert.Fail("Exception was expected");
            }
            catch (Exception ex)
            {
                //---------------Test Result -----------------------
                Assert.IsInstanceOf<InvalidCastException>(ex);
                StringAssert.Contains("Unable to cast object of type", ex.Message);
                StringAssert.Contains("to type", ex.Message);
            }

        }

        [Test]
        public void Test_CompareBusinessObjects_WhenMergeBOPropsAreSame_ShouldReturnTrue()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateIdenticalOnePropBOs(out xBO, out yBO);
            //---------------Assert Precondition----------------
            //Assert.AreEqual(_xBO.TestStringProp, _yBO.TestStringProp);
            Assert.AreEqual(xBO.ToString(), yBO.ToString());

            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(xBO, yBO);
            //---------------Test Result -----------------------
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareBusinessObjects_WhenMergeBOPropsDiffer_ShouldReturnFalse()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateDifferentOnePropBOs(out xBO, out yBO);
            //---------------Assert Precondition----------------
            Assert.AreNotEqual(xBO.ToString(), yBO.ToString());
            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(xBO, yBO);
            //---------------Test Result -----------------------
            Assert.IsFalse(result.AreEqual);
        }

        [Test]
        public void Test_CompareBusinessObjects_WhenOneBoSinglePropIsNull_ShouldReturnFalse()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateDifferentOnePropBOs(out xBO, out yBO);
            yBO.TestStringProp = null;
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = MergeCompareMergableBOs<MockOnePropertyBO>(xBO, yBO);
            //---------------Test Result -----------------------
            Assert.IsFalse(result.AreEqual);
        }

        [Test]
        public void Test_CompareBusinessObjects_WhenMergeBOPropsDiffer_ShouldReturnCompareResultWithFailureMessage()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateDifferentOnePropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();
            //---------------Assert Precondition----------------
            Assert.AreNotEqual(xBO.ToString(), yBO.ToString());
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CompareBusinessObjects(xBO, yBO);
            //---------------Test Result -----------------------
            var message = result.CompareFailureList[0].Message; 
            Assert.IsFalse(result.AreEqual);
            Assert.AreEqual(1, result.CompareFailureList.Count);
            StringAssert.Contains("is not equal to", message);
        }

        [Test]
        public void Test_CompareBusinessObjects_WhenMergeBOPropsAreSame_ShouldReturnCompareResultsWithNoFailureMessage()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateIdenticalOnePropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();
            CompareResult result = mergeBO.CompareBusinessObjects(xBO, yBO);
            //---------------Assert Precondition----------------
            Assert.AreEqual(xBO.ToString(), yBO.ToString());
            //---------------Execute Test ----------------------
            var compareFailures = result.CompareFailureList;
            //---------------Test Result -----------------------
            Assert.AreEqual(0, compareFailures.Count);
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenBothBOsAreNull_ShouldReturnCompareResultWithNoCompareFailures()
        {
            //---------------Set up test pack-------------------
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(null, null);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            Assert.AreEqual(0, compareFailures.Count);
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenXBOISNull_ShouldReturnCompareResultWithOneCompareFailure()
        {
            //---------------Set up test pack-------------------
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();

            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(null, new MockOnePropertyBO());
            //---------------Test Result -----------------------
            var message = result.CompareFailureList[0].Message;
            Assert.AreEqual(1, result.CompareFailureList.Count);
            Assert.IsFalse(result.AreEqual);
            StringAssert.Contains("fromMergableBO is null", message);
        }

        [Test]
        public void Test_CompareProps_WhenYBOIsNull_ShouldReturnCompareResultWithOneCompareFailure()
        {
            //---------------Set up test pack-------------------
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();

            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(new MockOnePropertyBO(), null);

            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            var message = compareFailures[0].Message; Assert.AreEqual(1, compareFailures.Count);
            Assert.IsFalse(result.AreEqual);
            StringAssert.Contains("toMergableBO is null", message);
        }

        [Test]
        public void Test_CompareProps_WhenOnePropBOsAreSame_ShouldReturnCompareResultWithNoCompareFailures()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateIdenticalOnePropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            Assert.AreEqual(0, compareFailures.Count);
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenOnePropBOsDiffer_ShouldReturnCompareResultWithOneCompareFailure()
        {
            //---------------Set up test pack-------------------
            MockOnePropertyBO xBO;
            MockOnePropertyBO yBO;
            CreateDifferentOnePropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockOnePropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            var message = compareFailures[0].Message;
            Assert.AreEqual(1, compareFailures.Count);
            Assert.IsFalse(result.AreEqual);
            StringAssert.Contains("is not equal to", message);
        }

        [Test]
        public void Test_CompareProps_WhenTwoPropBOsAreSame_ShouldReturnCompareResultWithNoCompareFailures()
        {
            //---------------Set up test pack-------------------
            MockTwoPropertyBO xBO;
            MockTwoPropertyBO yBO;
            CreateIdenticalTwoPropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockTwoPropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result =  mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            Assert.AreEqual(0, compareFailures.Count);
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenTwoPropBOsDiffer_ShouldReturnCompareResultWithTwoCompareFailure()
        {
            //---------------Set up test pack-------------------
            MockTwoPropertyBO xBO;
            MockTwoPropertyBO yBO;
            CreateDifferentTwoPropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockTwoPropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            var message = compareFailures[0].Message;
            var message2 = compareFailures[1].Message;
            Assert.AreEqual(2, compareFailures.Count);
            StringAssert.Contains("is not equal to", message);
            StringAssert.Contains("is not equal to", message2);
            Assert.IsFalse(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenManyPropBOsAreSame_ShouldReturnCompareResultWithNoCompareFailures()
        {
            //---------------Set up test pack-------------------
            MockManyPropertyBO xBO;
            MockManyPropertyBO yBO;
            CreateIdenticalManyPropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockManyPropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result = mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            Assert.AreEqual(0, compareFailures.Count);
            Assert.IsTrue(result.AreEqual);
        }

        [Test]
        public void Test_CompareProps_WhenManyPropBOsDiffer_ShouldReturnCompareResultWithManyCompareFailure()
        {
            //---------------Set up test pack-------------------
            MockManyPropertyBO xBO;
            MockManyPropertyBO yBO;
            CreateDifferentManyPropBOs(out xBO, out yBO);
            var mergeBO = new MergeBOSpy<MockManyPropertyBO>();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            CompareResult result =  mergeBO.CallCompareProps(xBO, yBO);
            //---------------Test Result -----------------------
            var compareFailures = result.CompareFailureList;
            var message = compareFailures[0].Message;
            var message2 = compareFailures[1].Message;
            var message3 = compareFailures[2].Message;
            Assert.AreEqual(3, compareFailures.Count);
            StringAssert.Contains("is not equal to", message);
            StringAssert.Contains("is not equal to", message2);
            StringAssert.Contains("is not equal to", message3);
            Assert.IsFalse(result.AreEqual);
        }

        private static void CreateIdenticalOnePropBOs(out MockOnePropertyBO xBO, out MockOnePropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            xBO = new MockOnePropertyBO { TestStringProp = testStringProp };
            yBO = new MockOnePropertyBO { TestStringProp = testStringProp };
        }

        private static void CreateDifferentOnePropBOs(out MockOnePropertyBO xBO, out MockOnePropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            string differentTestStringProp = GetRandomString();
            xBO = new MockOnePropertyBO { TestStringProp = testStringProp };
            yBO = new MockOnePropertyBO { TestStringProp = differentTestStringProp };
        }

        private static void CreateIdenticalTwoPropBOs(out MockTwoPropertyBO xBO, out MockTwoPropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            xBO = new MockTwoPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp };
            yBO = new MockTwoPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp };
        }

        private static void CreateDifferentTwoPropBOs(out MockTwoPropertyBO xBO, out MockTwoPropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            string differentTestStringProp = GetRandomString();
            xBO = new MockTwoPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp };
            yBO = new MockTwoPropertyBO { TestStringProp = differentTestStringProp, TestStringProp2 = differentTestStringProp };
        }

        private static void CreateIdenticalManyPropBOs(out MockManyPropertyBO xBO, out MockManyPropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            xBO = new MockManyPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp, TestStringProp3 = testStringProp};
            yBO = new MockManyPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp, TestStringProp3 = testStringProp };
        }

        private static void CreateDifferentManyPropBOs(out MockManyPropertyBO xBO, out MockManyPropertyBO yBO)
        {
            string testStringProp = GetRandomString();
            string differentTestStringProp = GetRandomString();
            xBO = new MockManyPropertyBO { TestStringProp = testStringProp, TestStringProp2 = testStringProp, TestStringProp3 = testStringProp };
            yBO = new MockManyPropertyBO { TestStringProp = differentTestStringProp, TestStringProp2 = differentTestStringProp, TestStringProp3 = differentTestStringProp };
        }


        private static CompareResult MergeCompareMergableBOs<T>(IMergableBo x, IMergableBo y) where T : class, IMergableBo
        {

            // Using resolve is slow
            //var mergeBO = MergeableBORegistry.Instance.Resolve<T>();

            var mergeBO = new MergeBO<T>();
            return mergeBO.CompareBusinessObjects(x, y);
        }


        private static string GetRandomString()
        {
            return RandomValueGen.GetRandomString();
        }
    }

    internal class MockOnePropertyBO : BusinessObject, IMergableBo
    {
        public string TestStringProp
        {
            get { return (string)GetPropertyValue("TestStringProp"); }
            set { SetPropertyValue("TestStringProp", value); }
        }

        public override string ToString()
        {
            return TestStringProp;
        }

        public ChangeType PendingChangeType { get; set; }

        public bool PendingChangeApproved { get; set; }
    }


    internal class MockTwoPropertyBO : BusinessObject, IMergableBo
    {
        public string TestStringProp
        {
            get { return (string)GetPropertyValue("TestStringProp"); }
            set { SetPropertyValue("TestStringProp", value); }
        }

        public string TestStringProp2
        {
            get { return (string)GetPropertyValue("TestStringProp2"); }
            set { SetPropertyValue("TestStringProp2", value); }
        }

        public override string ToString()
        {
            return string.Format("{0} {1}",TestStringProp, TestStringProp2);
        }

        public ChangeType PendingChangeType { get; set; }

        public bool PendingChangeApproved { get; set; }
    }

    internal class MockManyPropertyBO : BusinessObject, IMergableBo
    {
        public string TestStringProp
        {
            get { return (string)GetPropertyValue("TestStringProp"); }
            set { SetPropertyValue("TestStringProp", value); }
        }

        public string TestStringProp2
        {
            get { return (string)GetPropertyValue("TestStringProp2"); }
            set { SetPropertyValue("TestStringProp2", value); }
        }

        public string TestStringProp3
        {
            get { return (string)GetPropertyValue("TestStringProp3"); }
            set { SetPropertyValue("TestStringProp3", value); }
        }

        public override string ToString()
        {
            return string.Format("{0} {1} {2}", TestStringProp, TestStringProp2, TestStringProp3);
        }

        public ChangeType PendingChangeType { get; set; }

        public bool PendingChangeApproved { get; set; }
    }


    internal class MergeBOSpy<T> : MergeBO<T> where T : class, IMergableBo
    {
        
        //public IList<IBOError> CallCompareFailuresGetter()
        //{
        //    return CompareFailureList;
        //}

        public CompareResult CallCompareProps(IBusinessObject fromMergableBo, IBusinessObject toMergableBo)
        {
            return CompareProps(fromMergableBo, toMergableBo);
        }

    }
    // ReSharper restore InconsistentNaming
}